#pragma once
#include "Util/Util.hpp"
#include "Util/Log.hpp"
#include <mutex>
#include <assert.h>

#define NULL_INPUT 1
#define USER_EXIST 2
#define USER_NOT_EXIST 3

class user_table
{
public:
    user_table(const std::string &host,
               const std::string &user,
               const std::string &passwd,
               const std::string &dbname,
               unsigned int port)
    {
        // 创建数据库句柄、连接数据库服务器
        _mysql_handle = MySQL_Util::mysql_create(host, user, passwd, dbname, port);
        assert(_mysql_handle != NULL);
    }
    ~user_table()
    {
        // 销毁数据库句柄
        MySQL_Util::mysql_destory(_mysql_handle);
        _mysql_handle = NULL;
    }
    bool AddUser(Json::Value &user, int &error_code) // 注册时添加新用户
    {
        // 0.必须输入有用户名和密码才能注册(注意: 是user内必须得有["username"]和["password"]字段! ! !):
        if (user["username"].isNull() || user["password"].isNull())
        {
            LOG(LogLevel::ERROR) << "username or password is NULL";
            error_code = NULL_INPUT;
            return false;
        }

        // 1.确保用户名不存在
        Json::Value val;
        bool ret = Select_by_name(user["username"].asCString(), val);
        if (ret)
        {
            LOG(LogLevel::ERROR) << "user exist!";
            error_code = USER_EXIST;
            return false;
        }

        // 2.构建出一个插入的sql语句   (因为直接用string相加组成sql语句不美观,这里使用宏和sprintf)
        //                           这里还使用了mysql的内置函数password() ,将密码加密存储
#define INSERT_USER "insert into user values (null,'%s',password('%s'),1000,0,0);" // 注意末尾加分号;
        char buffer[4096] = {0};
        // 将一些数据格式化,并存到buffer里
        sprintf(buffer, INSERT_USER, user["username"].asCString(), user["password"].asCString());

        {
            std::lock_guard<std::mutex> lock(_mutex);

            // 3.执行插入SQL语句
            bool ret1 = MySQL_Util::mysql_execute(_mysql_handle, buffer);
            if (!ret1)
            {
                LOG(LogLevel::ERROR) << "insert user error";
                return false;
            }
        }

        return true;
    }
    bool Login(Json::Value &user, int &error_code) // 登录时验证并返回其他详细信息(都是使用user)
    {
        // 0.必须输入有用户名和密码才能登录(注意: 是user内必须得有["username"]和["password"]字段! ! !):
        if (user["username"].isNull() || user["password"].isNull())
        {
            LOG(LogLevel::ERROR) << "username or password is NULL";
            error_code = NULL_INPUT;
            return false;
        }

        // 以用户名和密码作为用户的筛选条件
        // 并获取id、积分、总场次、胜场次

        // 1.构建SQL语句    //注意: 因为数据库的密码是加密后的,所以需要将登录密码加密后再与数据库的密码比较
#define SELECT_USER "select id,score,total_count,win_count from user where username='%s' and password=password('%s');"
        char buffer[4096] = {0};
        sprintf(buffer, SELECT_USER, user["username"].asCString(), user["password"].asCString());

        MYSQL_RES *result;
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 2.执行SQL语句
            bool ret = MySQL_Util::mysql_execute(_mysql_handle, buffer);
            if (!ret)
            {
                LOG(LogLevel::ERROR) << "user login fail";
                return false;
            }
            // 到此,可能有一条数据,也可能没有，但一定不会是多条数据,因为用户名一定是唯一的！
            // 3.将查询结果保存到本地
            result = mysql_store_result(_mysql_handle); // 注意: mysql_store_result需要用到_mysql_handle,所以要在加锁范围内
            if (result == NULL)
            {
                // 可能用户存在,但是密码错了,也可能用户不存在.
                // 但这里统一认为是用户不存在
                LOG(LogLevel::ERROR) << "user is not exist";
                error_code = USER_NOT_EXIST;
                return false;
            }
        }

        MYSQL_ROW rows = mysql_fetch_row(result); // 遍历结果集,因为只有一行数据,所以只需要fetch一次

        // 4.将结果带出去:
        user["id"] = std::stol(rows[0]);    // 取出id,然后将字符串转成long
        user["score"] = std::stol(rows[1]); // 取出score
        user["total_count"] = std::stol(rows[2]);
        user["win_count"] = std::stol(rows[3]);

        return true;
    }

    bool Select_by_name(const std::string &name, Json::Value &user) // 通过用户名获取用户详细信息
    {
        // 以用户名为过滤条件,获取除了用户名和密码的其他信息:

        // 1.构建SQL语句
#define SELECT_USER_BY_NAME "select id,score,total_count,win_count from user where username='%s';"
        // 2.格式化SQL语句
        char buffer[4096] = {0};
        sprintf(buffer, SELECT_USER_BY_NAME, name.c_str());

        MYSQL_RES *result = NULL;
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 3.执行SQL语句
            MySQL_Util::mysql_execute(_mysql_handle, buffer);

            // 到此,可能有一条数据,也可能没有，但一定不会是多条数据,因为用户名一定是唯一的！
            // 4.将查询结果保存到本地
            result = mysql_store_result(_mysql_handle);
        }

        // 有几行数据
        int row_num = mysql_num_rows(result);
        if (row_num == 0) // 没有该用户
        {
            return false;
        }

        // 5.遍历结果集,因为只有一行数据,所以只需要遍历一次
        MYSQL_ROW rows = mysql_fetch_row(result);

        // 6.将结果带出去:
        user["id"] = std::stol(rows[0]);    // 取出id,然后将字符串转成long
        user["score"] = std::stol(rows[1]); // 取出score
        user["total_count"] = std::stol(rows[2]);
        user["win_count"] = std::stol(rows[3]);

        // 7.释放结果集
        mysql_free_result(result);
        return true;
    }
    bool Select_by_id(int id, Json::Value &user) // 通过id获取用户详细信息
    {
        // 以id为过滤条件, 获取除了id和密码的其他信息 :

        // 1.构建SQL语句
#define SELECT_USER_BY_ID "select username,score,total_count,win_count from user where id=%d;"

        // 2.格式化SQL语句
        char buffer[4096] = {0};
        sprintf(buffer, SELECT_USER_BY_ID, id);

        MYSQL_RES *result;
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 3.执行SQL语句
            bool ret = MySQL_Util::mysql_execute(_mysql_handle, buffer);
            if (!ret)
            {
                LOG(LogLevel::ERROR) << "search user fail";
                return false;
            }

            // 到此,可能有一条数据,也可能没有，但一定不会是多条数据,因为用户名一定是唯一的！
            // 4.将查询结果保存到本地
            result = mysql_store_result(_mysql_handle);
            if (result == NULL) // 用户不存在
            {
                LOG(LogLevel::ERROR) << "user is not exist";
                return false;
            }
        }

        // 5.遍历结果集,因为只有一行数据,所以只需要遍历一次
        MYSQL_ROW rows = mysql_fetch_row(result);

        // 6.将结果带出去:
        user["username"] = rows[0];         // 取出username
        user["score"] = std::stol(rows[1]); // 取出score
        user["total_count"] = std::stol(rows[2]);
        user["win_count"] = std::stol(rows[3]);

        // 7.释放结果集
        mysql_free_result(result);
        return true;
    }

    // 规则: 胜利一次+25积分
    bool win(int id) // 胜利后更新 积分、总场次、胜场
    {
        std::string update_sql =
            "update user set score=score+25,total_count=total_count+1,win_count=win_count+1 where id=" + std::to_string(id) + ";";

        {
            std::lock_guard<std::mutex> lock(_mutex);
            bool ret = MySQL_Util::mysql_execute(_mysql_handle, update_sql);
            if (!ret)
            {
                LOG(LogLevel::ERROR) << "update data fail";
                return false;
            }
        }
        return true;
    }

    // 规则: 输一次-30积分
    bool lose(int id) // 失败后更新积分、总场次
    {
        std::string update_sql =
            "update user set score=score-30,total_count=total_count+1 where id=" + std::to_string(id) + ";";

        {
            std::lock_guard<std::mutex> lock(_mutex);
            bool ret = MySQL_Util::mysql_execute(_mysql_handle, update_sql);
            if (!ret)
            {
                LOG(LogLevel::ERROR) << "update data fail";
                return false;
            }
        }

        return true;
    }

private:
    MYSQL *_mysql_handle; // 需要通过数据库的句柄来操作表
    std::mutex _mutex;    // 在多线程场景下,可能有线程对表正在查询、也有的对表进行插入,所以需要加锁
};
